package com.py.framework.core.plugin;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.util.CollectionUtils;

import com.py.framework.core.common.Page;
import com.py.framework.core.ds.DataSourceContextHolder;

/**
 * 通过拦截<code>StatementHandler</code>的<code>prepare</code>方法，重写sql语句实现物理分页。
 * 
 * @author
 * 
 */
@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }) })
public class PageInterceptor implements Interceptor {
	private static final Log logger = LogFactory.getLog(PageInterceptor.class);
	private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
	private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
	private static final ReflectorFactory DEFAULT_REFLECTOR_FACTORY = new DefaultReflectorFactory();
	private static Map<String, String> dataSourceMap; // 数据源类型
	private static String defaultDialect = "mysql"; // 数据库类型
	private static String defaultPageSqlId = ".*Page$"; // 需要拦截的ID(正则匹配)
	private static String advancedSearchSqlId = ".*ByCondition\\w+";
	private static String defaultDataSource; // 默认的数据源
	private static String dialect = ""; // 数据库类型(默认为mysql)
	private static String pageSqlId = ""; // 需要拦截的ID(正则匹配)

	public static void setDialect(String dialect) {
		PageInterceptor.dialect = dialect;
	}

	public static void setPageSqlId(String pageSqlId) {
		PageInterceptor.pageSqlId = pageSqlId;
	}

	/**
	 * @param defaultDataSource
	 *            the defaultDataSource to set
	 */
	public static void setDefaultDataSource(String defaultDataSource) {
		PageInterceptor.defaultDataSource = defaultDataSource;
	}

	/**
	 * @param dataSourceMap
	 *            the dataSourceMap to set
	 */
	public static void setDataSourceMap(Map<String, String> dataSourceMap) {
		PageInterceptor.dataSourceMap = dataSourceMap;
	}

	@SuppressWarnings("unchecked")
	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
		MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, DEFAULT_OBJECT_FACTORY,
				DEFAULT_OBJECT_WRAPPER_FACTORY, DEFAULT_REFLECTOR_FACTORY);
		// 分离代理对象链(由于目标类可能被多个拦截器拦截，从而形成多次代理，通过下面的两次循环可以分离出最原始的的目标类)
		while (metaStatementHandler.hasGetter("h")) {
			Object object = metaStatementHandler.getValue("h");
			metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY,
					DEFAULT_REFLECTOR_FACTORY);
		}
		// 分离最后一个代理对象的目标类
		while (metaStatementHandler.hasGetter("target")) {
			Object object = metaStatementHandler.getValue("target");
			metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY,
					DEFAULT_REFLECTOR_FACTORY);
		}
		/**************************/
		// 设置当前操作的数据库类型
		String dbType = DataSourceContextHolder.getDbType();
		if (StringUtils.isBlank(dbType) && null == dataSourceMap) {
			dialect = defaultDialect;
		} else {
			if (StringUtils.isBlank(dbType)) {
				if (StringUtils.isNotBlank(defaultDataSource) && null != dataSourceMap
						&& dataSourceMap.containsKey(defaultDataSource)
						&& StringUtils.isNotBlank(dataSourceMap.get(defaultDataSource))) {
					dialect = dataSourceMap.get(defaultDataSource);
				} else {
					dialect = defaultDialect;
				}
			} else {
				if (null != dataSourceMap && dataSourceMap.containsKey(dbType)
						&& StringUtils.isNotBlank(dataSourceMap.get(dbType))) {
					dialect = dataSourceMap.get(dbType);
				} else {
					dialect = defaultDialect;
				}
			}
		}
		if (StringUtils.isBlank(pageSqlId)) {
			pageSqlId = defaultPageSqlId;
		}
		/**************************/

		MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");

		// 高级搜索
		if (mappedStatement.getId().matches(advancedSearchSqlId)) {

			BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
			Object parameterObject = boundSql.getParameterObject();
			if (parameterObject == null) {
				throw new NullPointerException("parameterObject is null!");
			} else {

				// 高级搜索
				List<String> conditions = (List<String>) metaStatementHandler
						.getValue("delegate.boundSql.parameterObject.conditions");

				List<String> columns = (List<String>) metaStatementHandler
						.getValue("delegate.boundSql.parameterObject.columns");

				String[] additionalcolumns = (String[]) metaStatementHandler
						.getValue("delegate.boundSql.parameterObject.additionalColumns");

				String sql = boundSql.getSql();

				// 重写sql
				String advancedSearchSql = buildAdvanceSearchSql(conditions, columns, additionalcolumns, sql);

				metaStatementHandler.setValue("delegate.boundSql.sql", advancedSearchSql);

			}

		}
		// 只重写需要分页的sql语句。通过MappedStatement的ID匹配，默认重写以Page结尾的MappedStatement的sql
		if (mappedStatement.getId().matches(pageSqlId)) {
			BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
			Object parameterObject = boundSql.getParameterObject();
			if (parameterObject == null) {
				throw new NullPointerException("parameterObject is null!");
			} else {
				Page page = (Page) metaStatementHandler.getValue("delegate.boundSql.parameterObject.page");
				String sql = boundSql.getSql();

				// 重写sql
				String pageSql = buildPageSql(sql, page);

				metaStatementHandler.setValue("delegate.boundSql.sql", pageSql);
				// 采用物理分页后，就不需要mybatis的内存分页了，所以重置下面的两个参数
				metaStatementHandler.setValue("delegate.rowBounds.offset", RowBounds.NO_ROW_OFFSET);
				metaStatementHandler.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT);
				Connection connection = (Connection) invocation.getArgs()[0];
				// 重设分页参数里的总页数等
				setPageParameter(sql, connection, mappedStatement, boundSql, page);
			}
		}

		// 将执行权交给下一个拦截器
		return invocation.proceed();
		// return null;
	}

	/**
	 * @param conditions
	 * @param columns
	 * @param additionalcolumns
	 * @param sql
	 * @return
	 */
	private String buildAdvanceSearchSql(List<String> conditions, List<String> columns, String[] additionalcolumns,
			String sql) {

		StringBuilder advanceSearchSql = new StringBuilder();

		advanceSearchSql.append("select ");

		if (additionalcolumns != null) {

			for (String col : additionalcolumns) {

				advanceSearchSql.append(col).append(",");
			}

		}

		for (String col : columns) {

			advanceSearchSql.append(col).append(",");
		}

		advanceSearchSql.setLength(advanceSearchSql.length() - ",".length());

		advanceSearchSql.append(" from (").append(sql).append(") temp");

		if (!CollectionUtils.isEmpty(conditions)) {

			advanceSearchSql.append(" where ");

			for (String condition : conditions) {

				advanceSearchSql.append(condition).append(" and ");
			}

			advanceSearchSql.setLength(advanceSearchSql.length() - " and ".length());

		}

		return advanceSearchSql.toString();

	}

	/**
	 * 从数据库里查询总的记录数并计算总页数，回写进分页参数<code>PageParameter</code>,这样调用者就可用通过 分页参数
	 * <code>PageParameter</code>获得相关信息。
	 * 
	 * @param sql
	 * @param connection
	 * @param mappedStatement
	 * @param boundSql
	 * @param page
	 */
	private void setPageParameter(String sql, Connection connection, MappedStatement mappedStatement, BoundSql boundSql,
			Page page) {
		// 记录总记录数
		String countSql = "select count(0) from (" + sql + ") total";
		PreparedStatement countStmt = null;
		ResultSet rs = null;
		try {
			countStmt = connection.prepareStatement(countSql);
			BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql,
					boundSql.getParameterMappings(), boundSql.getParameterObject());
			setParameters(countStmt, mappedStatement, countBS, boundSql.getParameterObject());
			rs = countStmt.executeQuery();
			int totalCount = 0;
			if (rs.next()) {
				totalCount = rs.getInt(1);
			}
			page.setTotalCount(totalCount);
			int totalPage = totalCount / page.getLimit() + ((totalCount % page.getLimit() == 0) ? 0 : 1);
			page.setTotalPage(totalPage);

		} catch (SQLException e) {
			logger.error("Ignore this exception", e);
		} finally {
			try {
				rs.close();
			} catch (SQLException e) {
				logger.error("Ignore this exception", e);
			}
			try {
				countStmt.close();
			} catch (SQLException e) {
				logger.error("Ignore this exception", e);
			}
		}

	}

	/**
	 * 对SQL参数(?)设值
	 * 
	 * @param ps
	 * @param mappedStatement
	 * @param boundSql
	 * @param parameterObject
	 * @throws SQLException
	 */
	private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql,
			Object parameterObject) throws SQLException {
		ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
		parameterHandler.setParameters(ps);
	}

	/**
	 * 根据数据库类型，生成特定的分页sql
	 * 
	 * @param sql
	 * @param page
	 * @return
	 */
	private String buildPageSql(String sql, Page page) {
		if (page != null) {
			StringBuilder pageSql = new StringBuilder();
			if ("mysql".equals(dialect)) {
				pageSql = buildPageSqlForMysql(sql, page);
			} else if ("oracle".equals(dialect)) {
				pageSql = buildPageSqlForOracle(sql, page);
			} else {
				return sql;
			}
			return pageSql.toString();
		} else {
			return sql;
		}
	}

	/**
	 * mysql的分页语句
	 * 
	 * @param sql
	 * @param page
	 * @return String
	 */
	public StringBuilder buildPageSqlForMysql(String sql, Page page) {
		StringBuilder pageSql = new StringBuilder(100);
		String beginrow = String.valueOf((page.getPage() - 1) * page.getLimit());
		pageSql.append(sql);
		if (null != page.getOrderBy() && !"".equals(page.getOrderBy())) {
			pageSql.append(" order by " + page.getOrderBy());
			if (null != page.getOrder() && !"".equals(page.getOrder())) {
				pageSql.append(" " + page.getOrder() + " ");
			}
		}
		pageSql.append(" limit " + beginrow + "," + page.getLimit());
		return pageSql;
	}

	/**
	 * 参考hibernate的实现完成oracle的分页
	 * 
	 * @param sql
	 * @param page
	 * @return String
	 */
	public StringBuilder buildPageSqlForOracle(String sql, Page page) {
		StringBuilder pageSql = new StringBuilder(100);
		String beginrow = String.valueOf((page.getPage() - 1) * page.getLimit());
		String endrow = String.valueOf(page.getPage() * page.getLimit());

		pageSql.append("select * from ( select temp.*, rownum row_id from ( ");
		pageSql.append(sql);
		pageSql.append(" ) temp where rownum <= ").append(endrow);
		pageSql.append(") where row_id > ").append(beginrow);
		return pageSql;
	}

	@Override
	public Object plugin(Object target) {
		// 当目标类是StatementHandler类型时，才包装目标类，否者直接返回目标本身,减少目标被代理的次数
		if (target instanceof StatementHandler) {
			return Plugin.wrap(target, this);
		} else {
			return target;
		}
	}

	@Override
	public void setProperties(Properties properties) {
	}

}